﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OAuth.Net.ServiceProvider;
using OAuth.Net.Consumer;
using OAuth.Net.Components;
using System.Collections.Specialized;
using OAuth.Net.Common;
using System.Net;
using System.IO;
using System.Xml;
using System.Runtime.Serialization.Formatters.Binary;
using System.Configuration;
using Castle.Windsor;

namespace NFAPI
{
    public class NetflixService : INetflixService
    {
        #region Properties
        private OAuthService _getService;
        private OAuthService _postService;
        private OAuthService _deleteService;
        private IToken _requestToken;
        private IToken _accessToken;
        private string _userID;
        private string _callbackUrl;
        private string _tokenKey;

        public IToken RequestToken
        {
            get { return _requestToken; }
            set { _requestToken = value; }
        }

        public IToken AccessToken {
            get { return _accessToken; }
            set { _accessToken = value; }
        }

        public string UserId {
            get { return _userID; }
            set { _userID = value; }
        }

        public string CallbackUrl {
            get { return _callbackUrl; }
            set { _callbackUrl = value; }
        }

        public Uri AuthenticationUri
        {
            get
            {
                NameValueCollection parameters = new NameValueCollection();
                parameters.Add("application_name", _getService.Consumer.FriendlyName);
                parameters.Add("oauth_consumer_key", _getService.Consumer.Key);
                if (_callbackUrl != null && _callbackUrl != string.Empty)
                    parameters.Add("oauth_callback", _callbackUrl);
                return _getService.BuildAuthorizationUrl(RequestToken, parameters);
            }
        }

        private static Configuration.NetflixServiceConfigurationSection Config = (Configuration.NetflixServiceConfigurationSection)ConfigurationManager.GetSection("Netflix");
        private ITokenStore _tokenStore = Config.GetTokenStore();
        #endregion

        #region Constructors

        public NetflixService(string tokenKey)
        {
            MakeServices(tokenKey);
        }

        public NetflixService()
        {
            MakeServices("");
        }

        private void MakeServices(string tokenKey)
        {
            _getService = OAuthService.Create(
                new Uri("http://api.netflix.com/oauth/request_token"),
                new Uri("https://api-user.netflix.com/oauth/login"),
                new Uri("http://api.netflix.com/oauth/access_token"),
                "GET",
                false,
                string.Empty,
                "HMAC-SHA1",
                "1.0",
                new OAuthConsumer(Config.APIKey, Config.Secret, Config.ApplicationName));
            _postService = OAuthService.Create(
                new Uri("http://api.netflix.com/oauth/request_token"),
                new Uri("https://api-user.netflix.com/oauth/login"),
                new Uri("http://api.netflix.com/oauth/access_token"),
                "POST",
                false,
                string.Empty,
                "HMAC-SHA1",
                "1.0",
                new OAuthConsumer(Config.APIKey, Config.Secret, Config.ApplicationName));
            _deleteService = OAuthService.Create(
                new Uri("http://api.netflix.com/oauth/request_token"),
                new Uri("https://api-user.netflix.com/oauth/login"),
                new Uri("http://api.netflix.com/oauth/access_token"),
                "DELETE",
                false,
                string.Empty,
                "HMAC-SHA1",
                "1.0",
                new OAuthConsumer(Config.APIKey, Config.Secret, Config.ApplicationName));
            _tokenKey = tokenKey;
            if (_tokenStore != null)
            {
                _tokenStore.GetTokens(tokenKey, out _userID, out _requestToken, out _accessToken);
            }
        }
        #endregion

        #region Public Methods
        public string SearchTitles(string term, int startIndex, int maxResults)
        {
            NameValueCollection parameters = new NameValueCollection();
            parameters.Add("term", term);
            parameters.Add("start_index", startIndex.ToString());
            parameters.Add("max_results", maxResults.ToString());
            return new StreamReader(GetUnprotected("http://api.netflix.com/catalog/titles", parameters).GetResponseStream()).ReadToEnd();
        }

        public string GetTitleDetails(string uri)
        {
            NameValueCollection parameters = new NameValueCollection();
            return new StreamReader(GetUnprotected(uri, parameters).GetResponseStream()).ReadToEnd();
        }

        public string GetGenericResource(string uri)
        {
            NameValueCollection parameters = new NameValueCollection();
            return new StreamReader(GetUnprotected(uri, parameters).GetResponseStream()).ReadToEnd();
        }

        public string GetSimilars(string uri, int startIndex, int maxResults)
        {
            NameValueCollection parameters = new NameValueCollection();
            parameters.Add("start_index", startIndex.ToString());
            parameters.Add("max_results", startIndex.ToString());
            return new StreamReader(GetUnprotected(uri, parameters).GetResponseStream()).ReadToEnd();
        }

        public string GetDiscQueueAvailable(int startIndex)
        {
            NameValueCollection parameters = new NameValueCollection();
            parameters.Add("start_index", startIndex.ToString());
            parameters.Add("max_results", "25");
            return new StreamReader(GetUserResource("http://api.netflix.com/users/{userid}/queues/disc/available", parameters).GetResponseStream()).ReadToEnd();
        }

        public string GetDiscQueue(int startIndex)
        {
            NameValueCollection parameters = new NameValueCollection();
            parameters.Add("start_index", startIndex.ToString());
            parameters.Add("max_results", "25");
            return new StreamReader(GetUserResource("http://api.netflix.com/users/{userid}/queues/disc", parameters).GetResponseStream()).ReadToEnd();
        }

        public string UpdateDiscQueue(string titleRef, int newPosition, long etag)
        {
            NameValueCollection parameters = new NameValueCollection();
            parameters.Add("title_ref", titleRef);
            parameters.Add("format", "DVD");
            parameters.Add("position", newPosition.ToString());
            parameters.Add("etag", etag.ToString());
            return new StreamReader(Post(string.Format("http://api.netflix.com/users/{0}/queues/disc", UserId), parameters).GetResponseStream()).ReadToEnd();
        }

        public string GetInstantWatchQueue(int startIndex)
        {
            NameValueCollection parameters = new NameValueCollection();
            parameters.Add("start_index", startIndex.ToString());
            parameters.Add("max_results", "25");
            return new StreamReader(GetUserResource("http://api.netflix.com/users/{userid}/queues/instant", parameters).GetResponseStream()).ReadToEnd();
        }

        public string UpdateInstantWatchQueue(string titleRef, int newPosition, long etag)
        {
            NameValueCollection parameters = new NameValueCollection();
            parameters.Add("title_ref", titleRef);
            parameters.Add("position", newPosition.ToString());
            parameters.Add("etag", etag.ToString());
            return new StreamReader(Post(string.Format("http://api.netflix.com/users/{0}/queues/instant", UserId), parameters).GetResponseStream()).ReadToEnd();
        }

        public string DeleteQueueItem(string entry)
        {
            NameValueCollection parameters = new NameValueCollection();
            return new StreamReader(Delete(entry, parameters).GetResponseStream()).ReadToEnd();
        }

        #endregion

        #region Private Methods
        private OAuthResource GetUserResource(string uri, NameValueCollection parameters)
        {
            if (UserId != null && UserId != string.Empty)
                return Get(uri.Replace("{userid}", UserId), parameters);
            else
                return Get(uri, parameters);
        }

        private OAuthResource GetUnprotected(string uri, NameValueCollection parameters)
        {
            OAuthRequest rq = OAuthRequest.Create(
                new Uri(uri),
                _getService,
                new OAuthToken(TokenType.Request, "", "", ""),
                new OAuthToken(TokenType.Access, "", "", ""));

            rq.OnBeforeGetProtectedResource += new EventHandler<PreProtectedResourceRequestEventArgs>((object sender, PreProtectedResourceRequestEventArgs args) =>
                args.AccessToken = null);
            OAuthResponse response = rq.GetResource(parameters);

            if (response.HasProtectedResource)
            {
                return response.ProtectedResource;
            }
            else
            {
                throw new OAuthNotAuthorizedException();
            }
        }

        private OAuthResource Get(string uri, NameValueCollection parameters)
        {
            OAuthRequest rq = OAuthRequest.Create(
                new Uri(uri),
                _getService,
                RequestToken,
                AccessToken);

            rq.OnReceiveAccessToken += new EventHandler<AccessTokenReceivedEventArgs>(rq_OnReceiveAccessToken);
            OAuthResponse response = rq.GetResource(parameters);

            if (response.HasProtectedResource)
            {
                return response.ProtectedResource;
            }
            else
            {
                RequestToken = response.Token;
                SaveTokens();

                throw new OAuthNotAuthorizedException();
            }
        }

        private OAuthResource Post(string uri, NameValueCollection parameters)
        {
            OAuthRequest rq = OAuthRequest.Create(
                new Uri(uri),
                _postService,
                RequestToken,
                AccessToken);

            OAuthResponse response = rq.GetResource(parameters);

            if (response.HasProtectedResource)
            {
                return response.ProtectedResource;
            }
            else
            {
                RequestToken = response.Token;
                SaveTokens();

                throw new OAuthNotAuthorizedException();
            }
        }

        private OAuthResource Delete(string uri, NameValueCollection parameters)
        {
            OAuthRequest rq = OAuthRequest.Create(
                new Uri(uri),
                _deleteService,
                RequestToken,
                AccessToken);
            OAuthResponse response = rq.GetResource(parameters);

            if (response.HasProtectedResource)
            {
                return response.ProtectedResource;
            }
            else
            {
                RequestToken = response.Token;
                SaveTokens();

                throw new OAuthNotAuthorizedException();
            }
        }

        void rq_OnReceiveAccessToken(object sender, AccessTokenReceivedEventArgs e)
        {
            AccessToken = e.AccessToken;
            UserId = e.AdditionalParameters["user_id"];
            SaveTokens();

            OAuthRequest req = sender as OAuthRequest;
            req.ResourceUri = new Uri(req.ResourceUri.OriginalString.Replace("{userid}", UserId));
        }

        void SaveTokens()
        {
            if (_tokenStore != null)
            {
                _tokenStore.SaveTokens(_tokenKey, _userID, _requestToken, _accessToken);
            }
        }

        #endregion

    }

    public class OAuthNotAuthorizedException : Exception { }
    public class ResourceProtectedException : Exception { }
}
